<?php
namespace ITSociaal\Themes\Houtgrutter;

class TemplateProcessor
{
	/** @var object */
	protected $context;

	/**
	 * @param string $template
	 *
	 * @return array[]
	 */
	protected function findMatches($template)
	{
		$matches = [];

		preg_match_all(
			"/%%(\\w+)%%/",
			$template,
			$matches,
			PREG_OFFSET_CAPTURE|PREG_SET_ORDER
		);

		return array_map(function($match) {
			return [$match[0][1], strlen($match[0][0]), $match[1][0]];
		}, $matches);
	}

	/**
	 * @param string  $template
	 * @param array[] $matches
	 *
	 * @return string
	 */
	protected function processMatches($template, $matches)
	{
		return array_reduce(
			array_reverse($matches),
			[$this, "processSingleMatch"],
			$template
		);
	}

	/**
	 * @param string $carry
	 * @param array  $item
	 *
	 * @return string
	 */
	protected function processSingleMatch($carry, $item)
	{
		$replacement = $this->getValue($item[2]);

		return
			substr($carry, 0, $item[0]) .
			$replacement .
			substr($carry, $item[0] + $item[1]);
	}

	/**
	 * @param string $key
	 *
	 * @return string
	 */
	protected function getValue($key)
	{
		$context = $this->getContext();

		try {
			$ret = (string)call_user_func([$context, "get" . $key]);
		}
		catch (\Exception $ex) {
			/* Not sure what should be done in this case,
			 * safest thing probably is to pretend as if
			 * nothing happened.
			 */
			$ret = "%%$key%%";
		}

		return $ret;
	}

	/**
	 * Set context object to get tag values from
	 *
	 * @param object $context
	 *
	 * @return self
	 * @throws \ITSociaal\Themes\Houtgrutter\Exception\InvalidArgumentException
	 */
	public function setContext($context)
	{
		if (!is_object($context)) {
			throw new Exception\InvalidArgumentException(
				"\$context must be a valid object"
			);
		}

		$this->context = $context;

		return $this;
	}

	/**
	 * @return object
	 * @throws \ITSociaal\Themes\Houtgrutter\Exception\RuntimeException
	 */
	public function getContext()
	{
		$context = $this->context;

		if (null === $context) {
			throw new Exception\RuntimeException(
				"Context has not been set"
			);
		}

		return $context;
	}

	/**
	 * Parse $template and replace %%tags%%
	 *
	 * @param string $template
	 *
	 * @return string
	 */
	public function process($template)
	{
		return $this->processMatches(
			$template,
			$this->findMatches($template)
		);
	}
}

/* vi:set ts=4 sw=4 noet: */
